<!DOCTYPE html>

<html data-theme="dark" lang="zh-CN">
<head><meta name="description" content="音乐浏览 · Ruotong Music｜若通音乐 - 若通音乐（Ruotong Music）。页面：search。高品质商用音乐与音效，授权便捷，企业合作支持。">
<script nonce="IClalsvn2VqRJlbO-283bg">
   /* rt-pre-theme */
(function(){
  try{
    var t = localStorage.getItem('theme');
    if(!t){ t = (window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light'; }
    document.documentElement.setAttribute('data-theme', t);
  }catch(e){}
})();
  </script>
<meta content="default-src 'self'; script-src 'self' https://www.googletagmanager.com https://unpkg.com 'nonce-IClalsvn2VqRJlbO-283bg' 'strict-dynamic' 'report-sample'; style-src 'self' 'unsafe-inline' 'report-sample'; img-src 'self' data: https: blob:; font-src 'self' data: https:; connect-src 'self' https:; frame-src 'self' https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests" http-equiv="Content-Security-Policy"/>
<meta content="light dark" name="color-scheme"/>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1" name="viewport"/>
<title>
   音乐浏览 · Ruotong Music｜若通音乐
  </title>
<meta content="若通音乐：高品质商用音乐与音效平台，提供 BPM/Key、版本剪辑与分轨 Stems，波形预览与智能搜索，适用广告、短视频、影视与游戏制作。" name="description"/>












<style>
   /* Color board patch */
.cb-controls{display:grid;gap:14px}
.cb-picker .row{display:flex;gap:8px;align-items:center}
.cb-picker input.hex{width:110px;padding:6px 8px;border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--fg)}
.cb-picker input[type="color"]{width:40px;height:32px;border:0;padding:0;background:transparent}
.cb-grid{display:grid;grid-template-columns:repeat(12, minmax(0,1fr));gap:6px}
.cb-grid .swatch{width:28px;height:28px;border-radius:6px;border:1px solid rgba(0,0,0,.08);background:var(--sw);cursor:pointer}
.cb-grid .swatch:hover{transform:scale(1.04)}
.cb-selected{display:flex;align-items:center;gap:10px}
.cb-selected .value{font-family:ui-monospace, SFMono-Regular, Menlo, monospace}
.cb-slider{display:grid;gap:6px}
.cb-right .legend .moods{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.cb-right .dot{display:inline-block;width:12px;height:12px;border-radius:50%;background:var(--sw);margin-right:6px;border:1px solid rgba(0,0,0,.1)}
  </style>
<style>
   /* a11y helpers */
.skip-link{position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden;}
.skip-link:focus{left:16px;top:16px;width:auto;height:auto;z-index:10000;padding:10px 14px;background:#000;color:#fff;border-radius:6px;}
.visually-hidden{position:absolute!important;clip:rect(1px,1px,1px,1px);padding:0;border:0;height:1px;width:1px;overflow:hidden;white-space:nowrap;}
  </style>
<link href="/πÇÉµû░Φë▓τ¢ÿπÇæµò┤Σ╜ôτ╜æτ½Ö-v331/site/search.html" rel="canonical"/>
<meta content="website" property="og:type"/>
<meta content="Ruotong Music" property="og:site_name"/>
<meta content="音乐浏览 · Ruotong Music｜若通音乐" property="og:title"/>
<meta content="若通音乐：高品质商用音乐与音效平台，提供 BPM/Key、版本剪辑与分轨 Stems，波形预览与智能搜索，适用广告、短视频、影视与游戏制作。" property="og:description"/>
<meta content="/πÇÉµû░Φë▓τ¢ÿπÇæµò┤Σ╜ôτ╜æτ½Ö-v331/site/search.html" property="og:url"/>
<meta content="https://www.ruotongmusic.com/assets/og/cover.webp" property="og:image"/>
<meta content="summary_large_image" name="twitter:card"/>
<meta content="音乐浏览 · Ruotong Music" name="twitter:title"/>
<meta content="根据模板快速筛选音乐：场景 / 情绪 / 用途 / 乐器任意组合。" name="twitter:description"/>
<meta content="https://www.ruotongmusic.com/static/og-cover.png" name="twitter:image"/>
<script nonce="IClalsvn2VqRJlbO-283bg" type="application/ld+json">
   {"@context": "https://schema.org", "@type": "Organization", "name": "若通音乐", "url": "https://www.ruotongmusic.com", "logo": "https://www.ruotongmusic.com/assets/og/cover.webp"}
  </script>
<link href="/site.webmanifest" rel="manifest"/>
<meta content="#0b1220" name="theme-color"/>
<meta content="strict-origin-when-cross-origin" name="referrer"/>
<link href="/πÇÉµû░Φë▓τ¢ÿπÇæµò┤Σ╜ôτ╜æτ½Ö-v331/site/search.html" hreflang="zh-CN" rel="alternate"/>
<link href="/πÇÉµû░Φë▓τ¢ÿπÇæµò┤Σ╜ôτ╜æτ½Ö-v331/site/search.html?lang=en" hreflang="en-US" rel="alternate"/>
<script nonce="IClalsvn2VqRJlbO-283bg" type="application/ld+json">
   {"@context": "https://schema.org", "@type": "WebSite", "name": "若通音乐", "url": "https://www.ruotongmusic.com", "potentialAction": {"@type": "SearchAction", "target": "https://www.ruotongmusic.com/search.html?q={query}", "query-input": "required name=query"}}
  </script>
<script async="True" crossorigin="anonymous" nonce="IClalsvn2VqRJlbO-283bg" src="https://www.googletagmanager.com/gtag/js?id=G-XXXX123456">
</script>
<script nonce="IClalsvn2VqRJlbO-283bg">
   window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-XXXX123456');
  </script>

<meta content="dark light" name="color-scheme"/>
<script defer="True" nonce="IClalsvn2VqRJlbO-283bg" src="../assets/js/theme-init.js">
</script>





<link href="//unpkg.com" rel="dns-prefetch"/>
<link crossorigin="anonymous" href="https://unpkg.com" rel="preconnect"/>
<link href="//www.googletagmanager.com" rel="dns-prefetch"/>
<link crossorigin="anonymous" href="https://www.googletagmanager.com" rel="preconnect"/>
<link href="//www.ruotongmusic.com" rel="dns-prefetch"/>
<link crossorigin="anonymous" href="https://www.ruotongmusic.com" rel="preconnect"/>
<meta content="#000000" media="(prefers-color-scheme: dark)" name="theme-color"/>
<meta content="#ffffff" media="(prefers-color-scheme: light)" name="theme-color"/>



<link href="/assets/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"/>
<meta content="zh_CN" property="og:locale"/>



<!-- Favicon -->
<link href="/assets/favicon.svg" rel="icon" type="image/svg+xml"/>
<link href="/assets/favicon.ico" rel="icon" sizes="any"/>
<link href="/assets/favicon.png" rel="apple-touch-icon" sizes="180x180"/>


<script defer="" src="js/palette-click.js"></script>

<link href="/assets/favicon-32.png" rel="icon" sizes="32x32" type="image/png"/>
<link href="/assets/favicon-16.png" rel="icon" sizes="16x16" type="image/png"/>
<link href="https://unpkg.com" rel="preconnect"/>
<link href="https://www.googletagmanager.com" rel="preconnect"/>

</link><link href="/zh/search" hreflang="zh" rel="alternate"/><link href="/en/search" hreflang="en" rel="alternate"/><link href="/ja/search" hreflang="ja" rel="alternate"/>







<link rel="stylesheet" href="../assets/css/search.min.css">
</head>
<body>
<style>
.skip-to-content{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;}
.skip-to-content:focus{left:16px;top:16px;width:auto;height:auto;z-index:10000;background:#fff;padding:8px 12px;border:1px solid #000;}
:focus-visible{outline:2px solid currentColor;outline-offset:2px;}
</style>
<a class="skip-to-content" href="#main">跳转到主内容</a>


<a class="skip-link" href="#main-content">
   跳到主要内容
  </a>
<div class="visually-hidden" id="main-content">
</div>
<a class="skip-link" href="#main">
   跳到主要内容
  </a>
<header class="header">
<div class="container row" id="main">
<a class="logo" href="index-v2.html">
<img alt="Ruotong Music" decoding="async" height="26" loading="lazy" src="assets/logo.svg" width="26"/>
<strong>
      Ruotong Music
     </strong>
</a>
<nav aria-label="主导航">
<a aria-current="page" href="index-v2.html">
      音乐
     </a>
<a href="collections.html">
      合集
     </a>
<a href="pricing.html">
      价格
     </a>
<a href="support.html">
      支持
     </a>
</nav>
<div class="grow">
</div>
<div class="actions">
<button aria-label="切换明暗主题" aria-pressed="false" class="theme rt-theme-toggle icon-btn" id="rt-theme-toggle" type="button">
<svg aria-hidden="true" class="sun" height="16" viewbox="0 0 24 24" width="16">
<circle cx="12" cy="12" fill="currentColor" r="5">
</circle>
</svg>
<svg aria-hidden="true" class="moon" height="16" style="display:none" viewbox="0 0 24 24" width="16">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" fill="currentColor">
</path>
</svg>
<span class="rt-visually-hidden">
       切换主题
      </span>
</button>
<div class="input" role="search">
<svg aria-hidden="true" height="16" viewbox="0 0 24 24" width="16">
<path d="M9.5 3a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13zm7.6 12.2 3.4 3.4-1.4 1.4-3.4-3.4z" fill="currentColor">
</path>
</svg>
<input aria-label="搜索音乐" id="search-input" placeholder="搜索关键词" type="search"/>
<span class="kbd">
       Ctrl / ⌘ + K
      </span>
</div>
<button class="ghost" type="button">
      中文 / EN
     </button>
<button class="ghost" type="button">
      登录
     </button>
<a class="btn" href="pricing.html">
      开始使用
     </a>
</div>
</div>
</header>
<section class="section container" id="color-search">
<div aria-hidden="true" style="display:none">
<h1 id="tpl-title">
     音乐浏览
    </h1>
<div id="chip-wrap">
</div>
<button id="btn-clear" type="button">
</button>
<input aria-label="输入" id="inline-add"/>
</div>
<section class="section container color-integrated">
<h2 style="margin:0 0 12px">
     按颜色找音乐
    </h2>
<div id="color-board">
</div>
<div class="results" id="color-results" style="margin-top:12px; display:block">
</div>
</section>
<section class="tracks">
<div class="container">
<div aria-label="歌单筛选与排序" class="toolbar" role="region">
<div class="input">
<input aria-label="搜索歌单内曲目" id="pl-search" placeholder="在歌单内搜索…" type="search"/>
</div>
<div aria-live="polite" class="search-tip" id="search-tip">
</div>
<div class="sort">
<span>
        排序
       </span>
<select aria-label="排序选项" id="pl-sort">
<option value="default">
         推荐
        </option>
<option value="new">
         最新
        </option>
<option value="len">
         时长
        </option>
<option value="bpm">
         BPM
        </option>
<option value="title">
         标题
        </option>
</select>
</div>
</div>
<div aria-label="快速筛选标签" class="chips" id="pl-chips">
</div>
<table class="table">
<thead>
<tr>
<th style="width:44px">
</th>
<th>
         标题
        </th>
<th>
         标签
        </th>
<th>
         BPM
        </th>
<th>
         时长
        </th>
<th>
         授权
        </th>
<th>
         操作
        </th>
</tr>
</thead>
<tbody id="track-body">
</tbody>
</table>
<div class="visually-hidden" id="tracks-note">
      点击播放或快速试听整张歌单
     </div>
</div>
</section>
</section>
<div class="player">
<div class="container row">
<input aria-label="播放进度" class="seek" id="seek" max="1000" min="0" type="range" value="0"/>
<canvas aria-hidden="true" class="meter" height="10" id="meter" width="120">
</canvas>
<button aria-label="播放 / 暂停" class="icon-btn" id="btn-play" type="button">
<svg aria-hidden="true" height="16" viewbox="0 0 24 24" width="16">
<path d="M8 5v14l11-7z" fill="currentColor">
</path>
</svg>
</button>
<button aria-label="下一首" class="icon-btn" id="btn-skip" type="button">
<svg aria-hidden="true" height="16" viewbox="0 0 24 24" width="16">
<path d="M6 18l8.5-6L6 6v12zm9-12h2v12h-2V6z" fill="currentColor">
</path>
</svg>
</button>
<div>
<div class="title" id="player-title">
      未选择曲目
     </div>
<div class="meta" id="player-meta">
      选择上方列表中的任意曲目即可播放
     </div>
<div class="wf-wrap" style="margin-top:8px; min-height:64px;">
<div class="waveform" id="waveform">
</div>
<div class="wave-timeline" id="wave-timeline">
</div>
</div>
</div>
<div class="right">
<button aria-label="Studio 参考监听" aria-pressed="false" class="pill" id="btn-hq" type="button">
      Studio
     </button>
<button aria-label="循环播放" aria-pressed="false" class="pill" id="btn-loop" type="button">
      循环
     </button>
<progress id="progress" max="100" value="0">
</progress>
<button class="btn ghost" type="button">
      收藏清单
     </button>
<button class="btn primary" type="button">
      一键授权
     </button>
</div>
</div>
</div>
<div aria-hidden="true" class="cmdk">
<div aria-labelledby="cmdk-title" aria-modal="true" class="box" role="dialog">
<div class="head">
<svg aria-hidden="true" height="18" viewbox="0 0 24 24" width="18">
<path d="M9.5 3a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13zm7.6 12.2 3.4 3.4-1.4 1.4-3.4-3.4z" fill="currentColor">
</path>
</svg>
<input aria-label="瞬搜：输入曲名 / 标签 / 用途..." id="cmdk-input" placeholder="瞬搜：输入曲名 / 标签 / 用途..." type="text"/>
<span class="kbd">
      Esc
     </span>
</div>
<div aria-labelledby="cmdk-title" class="list" id="cmdk-list" role="listbox">
</div>
</div>
</div>
<footer>
<div class="container cols">
<div>
<img alt="logo" decoding="async" height="26" loading="lazy" src="assets/logo.svg" style="opacity:.7; margin-bottom:10px" width="26"/>
<div style="color:#c8d1df; font-weight:600; margin-bottom:8px">
      Ruotong Music
     </div>
<div style="max-width:420px">
      为创作者与品牌打造的商用音乐平台。5000 首精选 + 极速搜索 + 一键授权。
     </div>
</div>
<div>
<h4>
      产品
     </h4>
<a href="index-v2.html">
      音乐
     </a>
<a href="collections.html">
      合集
     </a>
<a href="pricing.html">
      价格
     </a>
</div>
<div>
<h4>
      支持
     </h4>
<a href="support.html">
      帮助中心
     </a>
<a href="policy/v2/legal/index.html">
      授权条款
     </a>
<a href="policy/v2/privacy-security/index.html">
      隐私与安全
     </a>
</div>
<div>
<h4>
      公司
     </h4>
<a href="about.html">
      关于我们
     </a>
<a href="business.html">
      商务合作
     </a>
</div>
</div>
</footer>
<script nonce="IClalsvn2VqRJlbO-283bg">
   // Monkey-patch window.Audio to capture created HTMLAudioElement instances
(function(){
  const NativeAudio = window.Audio;
  const created = [];
  window.__rt_getAudioEls = () => created;
  window.Audio = function(...args){
    const el = new NativeAudio(...args);
    try{ created.push(el); }catch(e){}
    return el;
  };
  // Preserve static props & prototype
  try { Object.assign(window.Audio, NativeAudio); } catch(e){}
  window.Audio.prototype = NativeAudio.prototype;
})();
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/app.js" defer>
</script>
<script crossorigin="anonymous" nonce="IClalsvn2VqRJlbO-283bg" src="https://unpkg.com/wavesurfer.js@7/dist/wavesurfer.min.js" defer>
</script>
<script crossorigin="anonymous" nonce="IClalsvn2VqRJlbO-283bg" src="https://unpkg.com/wavesurfer.js@7/dist/plugins/timeline.min.js" defer>
</script>
<script crossorigin="anonymous" nonce="IClalsvn2VqRJlbO-283bg" src="https://unpkg.com/wavesurfer.js@7/dist/plugins/hover.min.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/wavesurfer-player.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg">
   (function(){
      // Parse template / tags from URL
      const map = {
        corp60: { title: "企业宣传 · 60s", tokens: ["电子","合成器","轻快"] },
        ecom8:  { title: "电商转场 · 8s 循环", tokens: ["转场","清新"] },
        doc90:  { title: "纪录片空镜 · 90s", tokens: ["大气","弦乐","安静"] }
      };
      const url = new url(location.href);
      const tpl = url.searchParams.get("tpl");
      const customTags = url.searchParams.get("tags"); // e.g. tags=电子,合成器,轻快
      let tokens = [];
      let title = "音乐浏览";
      if (customTags) {
        tokens = customTags.split(",").map(s=>s.trim()).filter(Boolean);
      } else if (tpl && map[tpl]) {
        tokens = map[tpl].tokens.slice();
        title = map[tpl].title;
      }
      document.getElementById("tpl-title").textContent = title;

      const wrap = document.getElementById("chip-wrap");
      function addChip(label){
        const btn = document.createElement("button");
        btn.className = "chip";
        btn.dataset.active = "false";
        btn.textContent = label;
        wrap.appendChild(btn);
      }
      tokens.forEach(addChip);

      // Support typing to add chips
      const addInput = document.getElementById("inline-add");
      addInput.addEventListener("keydown", (e)=>{
        if(e.key === "Enter"){
          const val = addInput.value.trim();
          if(val){
            addChip(val);
            addInput.value = "";
            // let app.js take over chip click → filter()
            // activate it immediately:
            setTimeout(()=>{
              const btn = Array.from(document.querySelectorAll(".hero .chip")).find(b => b.textContent.trim()===val);
              if(btn) btn.click();
            }, 0);
          }
        }
      });

      // Clear chips
      document.getElementById("btn-clear").addEventListener("click", ()=>{
        // Turn off all active chips
        document.querySelectorAll(".hero .chip[data-active='true']").forEach(b => b.click());
        // Remove all template chips from UI
        wrap.innerHTML = "";
      });

      // Activate default chips AFTER app.js has bound its listeners
      window.addEventListener("load", () => {
        const all = Array.from(document.querySelectorAll(".hero .chip"));
        all.forEach(btn => {
          if(tokens.includes(btn.textContent.trim())){
            btn.click();
          }
        });
      });
    })();
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg">
   document.querySelectorAll('.kbd').forEach(k => k.addEventListener('click', () => {
      const box = document.querySelector('.cmdk');
      if(box){ box.setAttribute('aria-hidden','false'); document.getElementById('cmdk-input').focus(); }
    }));
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/auth.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg">
   // Color utils: HEX↔Lab + ΔE2000
(function(){
  function hexToRgb(hex){
    var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    if(!m) return null;
    return { r: parseInt(m[1],16), g: parseInt(m[2],16), b: parseInt(m[3],16) };
  }
  function rgbToXyz(rgb){
    var r=rgb.r/255, g=rgb.g/255, b=rgb.b/255;
    function lin(v){ return v<=0.04045? v/12.92 : Math.pow((v+0.055)/1.055, 2.4); }
    r=lin(r); g=lin(g); b=lin(b);
    return { x:(r*0.4124564 + g*0.3575761 + b*0.1804375)*100,
             y:(r*0.2126729 + g*0.7151522 + b*0.0721750)*100,
             z:(r*0.0193339 + g*0.1191920 + b*0.9503041)*100 };
  }
  function xyzToLab(xyz){
    var xr=xyz.x/95.047, yr=xyz.y/100.0, zr=xyz.z/108.883;
    function f(t){ return t>0.008856 ? Math.cbrt(t) : (7.787*t + 16/116); }
    var fx=f(xr), fy=f(yr), fz=f(zr);
    return { L:116*fy-16, a:500*(fx-fy), b:200*(fy-fz) };
  }
  function hexToLab(hex){ var rgb=hexToRgb(hex); return rgb? xyzToLab(rgbToXyz(rgb)) : null; }
  function deltaE2000(L1){
    return function(L2){
      var L_1=L1.L, a_1=L1.a, b_1=L1.b, L_2=L2.L, a_2=L2.a, b_2=L2.b;
      var avgLp=(L_1+L_2)/2;
      var C1=Math.hypot(a_1,b_1), C2=Math.hypot(a_2,b_2);
      var avgC=(C1+C2)/2;
      var G=0.5*(1-Math.sqrt(Math.pow(avgC,7)/(Math.pow(avgC,7)+Math.pow(25,7))));
      var a1p=(1+G)*a_1, a2p=(1+G)*a_2;
      var C1p=Math.hypot(a1p,b_1), C2p=Math.hypot(a2p,b_2);
      var avgCp=(C1p+C2p)/2;
      function hp(b,a){ var h=Math.atan2(b,a)*180/Math.PI; return h>=0?h:h+360; }
      var h1p=hp(b_1,a1p), h2p=hp(b_2,a2p);
      var dLp=L_2-L_1, dCp=C2p-C1p;
      function dh(h1,h2,C1p,C2p){ if (C1p*C2p===0) return 0; var diff=h2-h1; if (Math.abs(diff)<=180) return diff; return diff>180? diff-360 : diff+360; }
      var dhp=dh(h1p,h2p,C1p,C2p);
      var dHp=2*Math.sqrt(C1p*C2p)*Math.sin((dhp*Math.PI/180)/2);
      function avgH(h1,h2,C1p,C2p){ if (C1p*C2p===0) return h1+h2; var diff=Math.abs(h1-h2); if (diff<=180) return (h1+h2)/2; return (h1+h2+360)/2; }
      var avgHp=avgH(h1p,h2p,C1p,C2p);
      var T=1-0.17*Math.cos((avgHp-30)*Math.PI/180)+0.24*Math.cos(2*avgHp*Math.PI/180)+0.32*Math.cos((3*avgHp+6)*Math.PI/180)-0.20*Math.cos((4*avgHp-63)*Math.PI/180);
      var dRo=30*Math.exp(-Math.pow((avgHp-275)/25,2));
      var Rc=2*Math.sqrt(Math.pow(avgCp,7)/(Math.pow(avgCp,7)+Math.pow(25,7)));
      var Sl=1+(0.015*Math.pow((avgLp-50),2))/Math.sqrt(20+Math.pow((avgLp-50),2));
      var Sc=1+0.045*avgCp;
      var Sh=1+0.015*avgCp*T;
      var Rt=-Math.sin(2*(dRo*Math.PI/180))*Rc;
      var Kl=1,Kc=1,Kh=1;
      return Math.sqrt(Math.pow(dLp/(Kl*Sl),2)+Math.pow(dCp/(Kc*Sc),2)+Math.pow(dHp/(Kh*Sh),2)+Rt*(dCp/(Kc*Sc))*(dHp/(Kh*Sh)));
    }
  }

  // Build palette
  function hslToRgb(h,s,l){ s/=100; l/=100; var C=(1-Math.abs(2*l-1))*s; var X=C*(1-Math.abs((h/60)%2-1)); var m=l-C/2;
    var r=0,g=0,b=0;
    if (0<=h && h<60){ r=C; g=X; b=0; } else if (60<=h && h<120){ r=X; g=C; b=0; }
    else if (120<=h && h<180){ r=0; g=C; b=X; } else if (180<=h && h<240){ r=0; g=X; b=C; }
    else if (240<=h && h<300){ r=X; g=0; b=C; } else { r=C; g=0; b=X; }
    return { r:Math.round((r+m)*255), g:Math.round((g+m)*255), b:Math.round((b+m)*255) };
  }
  function rgbToHex(r,g,b){ var toHex=(n)=>n.toString(16).padStart(2,'0'); return '#' + toHex(r)+toHex(g)+toHex(b); }

  function buildPalette(rows, cols){
    // Legacy HSL grid with broader coverage and extra grayscale row appended
    var shades=[];
    rows = rows || 7; // more vertical range
    cols = cols || 12;
    for (var r=0;r<rows;r++){
      var l = 82 - r*(64/Math.max(1,(rows-1))); // 82 -> 18
      for (var c=0;c<cols;c++){
        var h = (c*(360/cols))%360, s = 84 - r*(40/Math.max(1,(rows-1))); // vary saturation a bit too
        var rgb = hslToRgb(h, Math.max(24, Math.min(95, s)), Math.max(12, Math.min(88, l)));
        shades.push({ hex: rgbToHex(rgb.r, rgb.g, rgb.b) });
      }
    }
    // add a grayscale row at the end to cover neutral colors
    for (var g=0; g<cols; g++){
      var v = Math.round(8 + g * (92/(cols-1))); // 8..100
      var rgb = hslToRgb(0, 0, v);
      shades.push({ hex: rgbToHex(rgb.r, rgb.g, rgb.b) });
    }
    return shades;
  }

  function renderBoard(mount, onChange){
    var state = { hex:'#6EF3FF', tol:18, recent:[] };
    var palette = buildPalette(7,12);
    var STORE_KEY = 'rm.cb';
    // helpers
    function normalizeHex(h){ if(!h) return null; h=h.trim(); if(h[0]!=='#') h='#'+h; if(h.length===4){ // #abc
        var r=h[1], g=h[2], b=h[3]; h = '#'+r+r+g+g+b+b;
      }
      return h.toUpperCase();
    }
    function rgbToHsl(r,g,b){
      r/=255; g/=255; b/=255;
      var max=Math.max(r,g,b), min=Math.min(r,g,b);
      var h,s,l=(max+min)/2;
      if(max===min){ h=s=0; }
      else {
        var d=max-min;
        s=l>0.5 ? d/(2-max-min) : d/(max+min);
        switch(max){
          case r: h=(g-b)/d+(g<b?6:0); break;
          case g: h=(b-r)/d+2; break;
          case b: h=(r-g)/d+4; break;
        }
        h/=6;
      }
      return { h: h*360, s:s*100, l:l*100 };
    }
    function hexToHsl(hex){
      var rgb = hexToRgb(hex);
      if(!rgb) return null;
      return rgbToHsl(rgb.r, rgb.g, rgb.b);
    }
    function buildPaletteFromColors(colors, maxCount){
      // Sort by H, then L, then S and pick evenly to form a tidy grid
      maxCount = maxCount || 7*12 + 12;
      var uniq = [];
      var seen = {};
      (colors||[]).forEach(function(c){
        var h = normalizeHex(c && (c.color || c.color_hex || c.hex || c));
        if(!h) return;
        if(!/^#?[0-9A-Fa-f]{6}$/.test(h)) return;
        if(seen[h]) return;
        seen[h] = true; uniq.push(h);
      });
      var enriched = uniq.map(function(hx){
        var hsl = hexToHsl(hx);
        return {hex:hx, h: (hsl?hsl.h:0), s: (hsl?hsl.s:0), l: (hsl?hsl.l:0)};
      });
      enriched.sort(function(a,b){
        if (Math.abs(a.h-b.h) > 1e-6) return a.h-b.h;
        if (Math.abs(a.l-b.l) > 1e-6) return a.l-b.l;
        return a.s-b.s;
      });
      // downsample if too many
      if (enriched.length > maxCount){
        var step = enriched.length / maxCount;
        var pick = [];
        for (var i=0; i<maxCount; i++){ pick.push(enriched[Math.floor(i*step)]); }
        enriched = pick;
      }
      // append a grayscale strip
      var cols = 12;
      var neutrals = [];
      for (var g=0; g<cols; g++){
        var v = Math.round(6 + g * (94/(cols-1))); // 6..100
        var rgb = hslToRgb(0,0,v);
        neutrals.push({hex: rgbToHex(rgb.r, rgb.g, rgb.b), h:0, s:0, l:v});
      }
      return enriched.concat(neutrals);
    }
    function fetchDistinctColors(){
      // Try several endpoints; ignore errors and resolve []
      return new Promise(function(resolve){
        // If global colors provided
        if (Array.isArray(window.__COLORS__)) { resolve(window.__COLORS__); return; }
        if (Array.isArray(window.TRACKS)) {
          try{
            var list = window.TRACKS.map(function(t){ return t.color || t.color_hex; }).filter(Boolean);
            resolve(list); return;
          }catch(e){}
        }
        // network fallbacks
        var origin = (function(){ try { return new url(location.href).origin; }catch(e){ return ''; }
  // --- RT Enhancement: listen to color searches from the color board ---
  document.addEventListener('rt:color:search', function(ev){
    try{
      var detail = ev && ev.detail || {};
      var hex = detail.hex;
      var tol = typeof detail.tol === 'number' ? detail.tol : 18;
      if (!hex) return;
      // Update the "匹配的音乐" section immediately
      queryByColor(hex, tol).then(function(list){ render(list, hex, tol); });
      // Smooth scroll into view if section is off-screen
      var sec = document.getElementById('color-search');
      if (sec) sec.scrollIntoView({behavior:'smooth', block:'start'});
    }catch(e){}
  });
  // --- End Enhancement ---
})();
        var tried = 0, done = false, out = [];
        function finish(){ if(!done){ done = true; resolve(out); } }
        function ok(res){ return res && res.ok ? res.json() : Promise.reject('bad'); }
        function tryFetch(url, pick){
          return fetch(url, { credentials:'same-origin' })
            .then(ok)
            .then(function(data){
              if(done) return;
              if (Array.isArray(data) && data.length){
                out = pick ? pick(data) : data;
                finish();
              } else {
                throw new Error('empty');
              }
            }).catch(function(){ tried++; if (tried>=3) finish(); });
        }
        // 1) /api/colors
        tryFetch(origin + '/api/colors', function(arr){ return arr; });
        // 2) /api/tracks?fields=color,color_hex
        tryFetch(origin + '/api/tracks?fields=color,color_hex&limit=8000', function(arr){
          return arr.map(function(t){ return t.color || t.color_hex; }).filter(Boolean);
        });
        // 3) /api/tracks (generic)
        tryFetch(origin + '/api/tracks', function(arr){
          return arr.map(function(t){ return t.color || t.color_hex; }).filter(Boolean);
        });
        // timeout safety
        setTimeout(finish, 1200);
      });
    }

    // hydrate state from storage
    try{
      var saved = JSON.parse(localStorage.getItem(STORE_KEY) || '{}');
      if (saved && typeof saved === 'object'){
        if (saved.hex) state.hex = saved.hex;
        if (typeof saved.tol === 'number') state.tol = saved.tol;
        if (Array.isArray(saved.recent)) state.recent = saved.recent.slice(0,10);
      }
    }catch(e){}

    function persist(){
      try { localStorage.setItem(STORE_KEY, JSON.stringify(state)); } catch(e){}
    }

    function ui(){
      var grid = palette.map(function(p){ return '<button class="swatch" data-hex="'+p.hex+'" style="--sw:'+p.hex+'"></button>'; }).join('');
      var recent = state.recent.map(function(h){ return '<button class="swatch" data-recent="'+h+'" style="--sw:'+h+'" title="'+h+'"></button>'; }).join('');
      mount.innerHTML =
        '<div class="cb wrap">\
          <div class="cb-left">\
            <div class="cb-controls">\
              <div class="cb-picker">\
                <label for="cb-color-input" class="label">从取色器选择</label>\
                <div class="row">\
                  <input id="cb-color-input" type="color" value="'+state.hex+'"/>\
                  <button class="pill" id="cb-eyedropper" type="button">吸管取色</button>\
                  <input id="cb-hex-input" type="text" value="'+state.hex+'" class="hex"/>\
                </div>\
              </div>\
              <div class="cb-grid">'+grid+'</div>\
              <div class="cb-selected">\
                <span class="label">已选颜色</span>\
                <span class="value">'+state.hex+'</span>\
              </div>\
              <div class="cb-slider">\
                <label for="cb-tolerance">匹配灵敏度 ΔE</label>\
                <input id="cb-tolerance" type="range" min="4" max="48" step="1" value="'+state.tol+'"/>\
              </div>\
              <div class="cb-actions">\
  <div id="scene-reco" class="scene-rec">\
    <div class="scene-title">适宜场景</div>\
    <div class="scene-content"><strong class="scene-tag">—</strong> 选择颜色后将自动推荐使用场景</div>\
  </div>\
</div>\
              <div class="cb-recent">\
                <span>最近使用</span>\
                <div class="row">'+recent+'</div>\
              </div>\
            </div>\
          </div>\
          <div class="cb-right">\
            <div class="legend">\
              <h4>颜色情绪图例</h4>\
              <ul class="moods">\
                <li><span class="dot" style="--sw:#6EF3FF"></span>科技 · 未来</li>\
                <li><span class="dot" style="--sw:#8BFF96"></span>自然 · 轻快</li>\
                <li><span class="dot" style="--sw:#FFD15F"></span>温暖 · 活力</li>\
                <li><span class="dot" style="--sw:#FF7D7D"></span>热烈 · 冲突</li>\
                <li><span class="dot" style="--sw:#FF8BD7"></span>甜美 · 浪漫</li>\
                <li><span class="dot" style="--sw:#9BA4FF"></span>治愈 · 宁静</li>\
                <li><span class="dot" style="--sw:#E9ECF1"></span>极简 · 明快</li>\
                <li><span class="dot" style="--sw:#0B0D12"></span>深邃 · 神秘</li>\
              </ul>\
              <p class="tip">提示：点击色块或使用取色器快速选择；搜索基于 ΔE2000 近似色匹配。</p>\
            </div>\
          </div>\
        </div>';

      // smoothSync: minimal UI updates without re-rendering the whole palette
      function smoothSync(newHex){
        try{
          var hex = newHex || state.hex;
          // 1) update selected value text
          var vEl = mount.querySelector('.cb-selected .value'); if(vEl) vEl.textContent = hex;
          // 2) sync inputs
          var hexEl = mount.querySelector('#cb-hex-input'); if(hexEl) hexEl.value = hex;
          var colorEl = mount.querySelector('#cb-color-input'); if(colorEl) colorEl.value = hex;
          // 3) optionally update recent strip minimally (no grid reflow)
          try{
            var row = mount.querySelector('.cb-left .cb-recent .row');
            if(row && !row.querySelector('button[data-recent="'+hex+'"]')){
              var btn = document.createElement('button');
              btn.className = 'swatch';
              btn.setAttribute('data-recent', hex);
              btn.style.setProperty('--sw', hex);
              btn.title = hex;
              btn.addEventListener('click', function(){
                state.hex = hex; persist(); smoothSync(hex); emit();
              });
              row.insertBefore(btn, row.firstChild);
            }
          }catch(_){}
        }catch(_){}
      }
// events
      mount.querySelectorAll('[data-hex]').forEach(function(el){
        el.addEventListener('click', function(){
          state.hex = normalizeHex(el.getAttribute('data-hex'));
          if (state.recent.indexOf(state.hex) === -1){
            state.recent.unshift(state.hex);
            if (state.recent.length>10) state.recent.pop();
          }
          persist(); smoothSync(state.hex); emit();
        });
      });
      var tol = mount.querySelector('#cb-tolerance');
      tol && tol.addEventListener('input', function(e){ state.tol = Number(e.target.value); persist(); emit(); });
      mount.querySelectorAll('[data-recent]').forEach(function(el){
        el.addEventListener('click', function(){
          state.hex = normalizeHex(el.getAttribute('data-recent')); persist(); smoothSync(state.hex); emit();
        });
      });
      var colorInput = mount.querySelector('#cb-color-input');

colorInput && colorInput.addEventListener('input', function(e){
  state.hex = normalizeHex(e.target.value);
  persist();
  // local, non-destructive updates to avoid destroying the <input type="color"> popup
  try{
    var vEl = mount.querySelector('.cb-selected .value');
    if(vEl) vEl.textContent = state.hex;
    var hexEl = mount.querySelector('#cb-hex-input');
    if(hexEl) hexEl.value = state.hex;
  }catch(_){}
  emit();
});

      // --- [PATCH] Ensure native color input writes to "最近使用" ---
      (function(){
        var timer = null;
        function commitRecent(v){
          var hex = normalizeHex(v);
          if(!hex) return;
          try{
            if (Array.isArray(state.recent)){
              var idx = state.recent.indexOf(hex);
              if (idx !== -1) state.recent.splice(idx, 1);
              state.recent.unshift(hex);
              if (state.recent.length > 10) state.recent.pop();
            } else {
              state.recent = [hex];
            }
          }catch(_){}
          persist();
          // 安全整块重绘并同步参数
          try{ ui(); emit(); }catch(_){}
        }
        if (colorInput){
          colorInput.addEventListener('change', function(e){ commitRecent(e.target && e.target.value); });
          colorInput.addEventListener('blur',   function(e){ commitRecent(e.target && e.target.value); });
          colorInput.addEventListener('input',  function(e){
            try{ clearTimeout(timer); }catch(_){}
            var v = e.target && e.target.value;
            timer = setTimeout(function(){ commitRecent(v); }, 300);
          });
        }
      })();
      // --- [/PATCH] ---
var hexInput = mount.querySelector('#cb-hex-input');

hexInput && hexInput.addEventListener('change', function(e){
  var v = normalizeHex(e.target.value); if(!v) return;
  state.hex = v; persist();
  try{
    var vEl = mount.querySelector('.cb-selected .value'); if(vEl) vEl.textContent = state.hex;
    var colorEl = mount.querySelector('#cb-color-input'); if(colorEl) colorEl.value = state.hex;
  }catch(_){}
  emit();
});

      var eye = mount.querySelector('#cb-eyedropper');
      if (eye){
        if (!('EyeDropper' in window)){ eye.setAttribute('disabled','true'); eye.title = '当前浏览器不支持吸管取色'; }
        eye.addEventListener('click', function(){
          try{
            var ed = new window.EyeDropper();
            // also push to recent when chosen by eyedropper
ed.open().then(function(res){
  if(res && res.sRGBHex){
    state.hex = normalizeHex(res.sRGBHex);
    try{
      if (Array.isArray(state.recent)){
        var idx = state.recent.indexOf(state.hex);
        if (idx !== -1) state.recent.splice(idx,1);
        state.recent.unshift(state.hex);
        if (state.recent.length>10) state.recent.pop();
      }else{
        state.recent = [state.hex];
      }
    }catch(_){}
    persist(); smoothSync(state.hex); emit();
  }
}).catch(function(){});
          } catch(e){}
        });
      }
      mount.querySelector('#cb-search').addEventListener('click', function(e){
        e.preventDefault();
        try{
          var url = 'color-playlist.html?hex=' + encodeURIComponent(state.hex.toUpperCase()) + '&tol=' + encodeURIComponent(String(state.tol));
          window.location.href = url;
        }catch(err){
          // 最保守回退：不带参数也至少能到达页面
          window.location.href='color-playlist.html';
        }
      });
    }

    function emit(){
      try {
        var u = new url(window.location.href);
        u.searchParams.set('hex', state.hex.toUpperCase()); u.searchParams.set('tol', String(state.tol));
        history.replaceState(null, '', u.toString());
      } catch(e){}
      if (typeof onChange === 'function') onChange({ hex: state.hex, lab: hexToLab(state.hex), tolerance: state.tol });
    }

    // Initial UI
    ui(); emit();

    // Try to hydrate palette from backend/distinct colors
    fetchDistinctColors().then(function(colors){
      if (colors && colors.length){
        palette = buildPaletteFromColors(colors, 7*12 + 12);
        ui(); // re-render with dataset-driven palette
      }
    }).catch(function(){});
  }

  // Integrate into search.html
  document.addEventListener('DOMContentLoaded', function(){
    var mount = document.getElementById('color-board');
    if(!mount) return;

    function render(list, hex, tol){
      var box = document.getElementById('color-results'); // can be absent now
      if (!list || !list.length){
        if (box) box.innerHTML = '<p class="pill"><span class="dot" style="background:'+hex+'"></span> 没有与该颜色相近的歌曲（ΔE≤'+tol+'）。</p>';
        var bodyEl = document.getElementById('track-body'); if(bodyEl) bodyEl.innerHTML = '';
        var heroCount = document.getElementById('hero-count'); if(heroCount) heroCount.textContent = '0 首 · 自动适配版权';
        var heroColor = document.getElementById('hero-color'); if(heroColor){ var hx = heroColor.querySelector('.hex'); if(hx) hx.textContent = (hex||'').toUpperCase(); var sw = heroColor.querySelector('.swatch'); if(sw) sw.style.background = hex; }
        return;
      }
      try{
        var heroColor = document.getElementById('hero-color');
        if(heroColor){
          var hx = heroColor.querySelector('.hex'); if(hx) hx.textContent = String(hex||'').toUpperCase();
          var sw = heroColor.querySelector('.swatch'); if(sw) sw.style.background = hex;
          heroColor.style.display = 'inline-flex';
        }
        var heroCount = document.getElementById('hero-count'); if(heroCount) heroCount.textContent = (list.length||0) + ' 首 · 自动适配版权';
      }catch(e){}
      function normHex(h){ if(!h) return '#27E6B8'; h = String(h).trim(); if(h[0]!=='#') h = '#'+h; return h.toUpperCase(); }
      function mapPreview(h){
        var H = normHex(h);
        if(/^#(FF7D7D|FF6B6B|D94C4C)/i.test(H)) return 'assets/previews/red_energy.wav';
        if(/^#(FFD15F|FFC53D|FFB020)/i.test(H)) return 'assets/previews/yellow_bright.wav';
        if(/^#(31D269|2ECC71|27AE60|8BFF96)/i.test(H)) return 'assets/previews/green_nature.wav';
        if(/^#(5BB9FF|4EA1FF|3388FF|9BA4FF)/i.test(H)) return 'assets/previews/blue_calm.wav';
        if(/^#(FF8BD7|FF77C8|FFA0E0)/i.test(H)) return 'assets/previews/pink_romance.wav';
        if(/^#(9BA4FF|8C93FF)/i.test(H)) return 'assets/previews/violet_serene.wav';
        if(/^#(27E6B8|1FBBA0|00C8A0)/i.test(H)) return 'assets/previews/teal_ambient.wav';
        if(/^#(323F48|5A6770|6B7780)/i.test(H)) return 'assets/previews/grey_minimal.wav';
        return 'assets/demo.wav';
      }
      function moodFromHex(h){
        var H = normHex(h);
        if(/^#(6EF3FF|27E6B8|1FBBA0)/i.test(H)) return ['科技','未来'];
        if(/^#(8BFF96|31D269)/i.test(H)) return ['自然','清新'];
        if(/^#(FFD15F|FFC53D)/i.test(H)) return ['温暖','明亮'];
        if(/^#(FF7D7D|D94C4C)/i.test(H)) return ['热烈','能量'];
        if(/^#(9BA4FF|5BB9FF)/i.test(H)) return ['梦幻','宁静'];
        if(/^#(FF8BD7|FFA0E0)/i.test(H)) return ['浪漫','甜美'];
        return ['极简','冷静'];
      }
      function rowHTML(it, idx){
        var bpm = it.bpm || (90 + (idx*11)%60);
        var len = it.len ? (''+it.len) : '00:' + String(10 + (idx*7)%30).padStart(2,'0');
        var tags = (it.tags || moodFromHex(it.hex||hex)).map(function(t){ return '<span class="chip ghost">'+t+'</span>'; }).join(' ');
        var lic = it.license || '商用·全球';
        var preview = it.preview || mapPreview(it.hex||hex);
        var title = it.title || ('色彩匹配 ' + (idx+1));
        var artist = it.artist || 'Ruotong Studio';
        return '<tr data-id="'+(it.id||idx+1)+'" data-preview="'+preview+'" data-title="'+title+'" data-artist="'+artist+'">\n'
          + '  <td><button class="icon-btn play" aria-label="播放" type="button">▶</button></td>\n'
          + '  <td><div class="title">'+title+'</div><div class="meta">'+artist+'</div></td>\n'
          + '  <td>'+tags+'</td>\n'
          + '  <td>'+ (it.bpm || bpm) +'</td>\n'
          + '  <td>'+ (it.len || len) +'</td>\n'
          + '  <td><span class="pill">'+lic+'</span></td>\n'
          + '  <td class="ops">\n'
          + '    <button class="icon-btn fav" title="收藏" type="button">★</button>\n'
          + '    <a class="icon-btn dl" title="下载预览" href="'+preview+'" download>⤓</a>\n'
          + '    <button class="icon-btn buy" title="加入购物车" type="button">🛒</button>\n'
          + '  </td>\n'
          + '  </tr>';
      }
      var bodyEl = document.getElementById('track-body');
      if(bodyEl){
        bodyEl.innerHTML = list.map(rowHTML).join('');
        (function bindInteractions(){
          function $(s,c){ return (c||document).querySelector(s); }
          var body = bodyEl;
          var rows = function(){ return Array.prototype.slice.call(body.querySelectorAll('tr')); };
          function playAt(i){
            var row = rows()[i]; if(!row) return;
            var url = row.getAttribute('data-preview');
            var title = row.getAttribute('data-title');
            var artist = row.getAttribute('data-artist');
            try{ var ws = window.__rt_ws; if(ws && ws.load){ ws.load(url); } }catch(e){}
            var tEl = $('#player-title'), mEl = $('#player-meta');
            if(tEl) tEl.textContent = title || '未选择曲目';
            if(mEl) mEl.textContent = artist || '';
          }
          var btnPlayAll = $('#btn-play-all');
          if(btnPlayAll){ btnPlayAll.addEventListener('click', function(){ playAt(0); }); }
          var btnShuffle = $('#btn-shuffle');
          if(btnShuffle){ btnShuffle.addEventListener('click', function(){ var r = rows(); if(!r.length) return; playAt(Math.floor(Math.random()*r.length)); }); }
          var plSearch = $('#pl-search');
          if(plSearch){
            plSearch.addEventListener('input', function(){
              var q = plSearch.value.trim().toLowerCase();
              var visible = 0;
              rows().forEach(function(r){
                var txt = r.textContent.trim().toLowerCase();
                var ok = !q || txt.indexOf(q) >= 0;
                r.style.display = ok ? '' : 'none';
                if (ok) visible++;
              });
              var tip = $('#search-tip');
              if (tip) tip.textContent = q ? ('找到 '+visible+' 首') : '';
            });
          }
          var plSort = $('#pl-sort');
          if(plSort){
            plSort.addEventListener('change', function(){
              var mode = plSort.value || 'default';
              var arr = rows();
              var keyFn = function(tr){
                if(mode==='title'){ return (tr.getAttribute('data-title')||''); }
                if(mode==='bpm'){ return parseInt(tr.children[3].textContent.trim()||'0',10); }
                if(mode==='len'){ var t=tr.children[4].textContent.trim(); var m=/^(\d+):(\d+)$/.exec(t); return m? (+m[1])*60+(+m[2]) : 0; }
                if(mode==='new'){ return -1*Math.random(); }
                return 0;
              };
              arr.sort(function(a,b){ var ka=keyFn(a), kb=keyFn(b); if(ka<kb)return -1; if(ka>kb)return 1; return 0; });
              arr.forEach(function(tr){ body.appendChild(tr); });
            });
          }
          body.addEventListener('click', function(e){
            var t = e.target;
            if (t.classList.contains('play')){
              var i = rows().indexOf(t.closest('tr'));
              playAt(i);
            } else if (t.classList.contains('fav')){
              t.classList.toggle('is-active');
            } else if (t.classList.contains('buy')){
              try{
                var tr = t.closest('tr');
                var id = tr && tr.getAttribute('data-id') || '';
                var title = tr && tr.getAttribute('data-title') || '';
                var artist = tr && tr.getAttribute('data-artist') || '';
                if (window.licenseModal && typeof window.licenseModal.open === 'function'){
                  window.licenseModal.open({ id: id, title: title, artist: artist });
                }else{
                  window.location.href='track.html' + (id ? ('?id='+encodeURIComponent(id)) : '');
                }
              }catch(e){}
            }
          });
        })();
      }
    }

    var DEMO = [
      { id: 1, title: '霓虹疾驰', artist:'RT Studio', color:'#6EF3FF' },
      { id: 2, title: '绿野轻吟', artist:'RT Studio', color:'#8BFF96' },
      { id: 3, title: '晨光序曲', artist:'RT Studio', color:'#FFD15F' },
      { id: 4, title: '烈焰之心', artist:'RT Studio', color:'#FF7D7D' },
      { id: 5, title: '桃气泡',   artist:'RT Studio', color:'#FF8BD7' },
      { id: 6, title: '月色航行', artist:'RT Studio', color:'#9BA4FF' },
      { id: 7, title: '深海回声', artist:'RT Studio', color:'#2A6CFB' },
      { id: 8, title: '治愈手札', artist:'RT Studio', color:'#FFC6A7' },
      { id: 9, title: '极简白噪', artist:'RT Studio', color:'#E9ECF1' },
      { id:10, title: '深空序章', artist:'RT Studio', color:'#0B0D12' }
    ];

    function queryByColor(hex, tol){
      // try backend
      return new Promise(function(resolve){
        try {
          var u = new url(location.href);
          var api = u.origin + '/api/tracks';
          var q = new URLSearchParams({ color: hex.replace('#',''), tol: String(tol), mode: 'lab' });
          fetch(api + '?' + q.toString(), { credentials:'same-origin' })
            .then(function(res){ if(!res.ok) throw new Error('HTTP '+res.status); return res.json(); })
            .then(function(data){
              if (Array.isArray(data) && data.length){
                resolve(data.map(function(x){ return { id:x.id, title:x.title, artist:x.artist, color:x.color||x.color_hex||hex, deltaE:x.deltaE }; }));
              } else { throw new Error('empty'); }
            }).catch(function(){
              // fallback to demo
              var L1 = hexToLab(hex), d = deltaE2000(L1);
              resolve(DEMO.map(function(t){ return Object.assign({}, t, { deltaE: d(hexToLab(t.color)) }); })
                           .filter(function(x){ return x.deltaE <= tol; })
                           .sort(function(a,b){ return a.deltaE-b.deltaE; }));
            });
        } catch(e){
          var L1 = hexToLab(hex), d = deltaE2000(L1);
          resolve(DEMO.map(function(t){ return Object.assign({}, t, { deltaE: d(hexToLab(t.color)) }); })
                       .filter(function(x){ return x.deltaE <= tol; })
                       .sort(function(a,b){ return a.deltaE-b.deltaE; }));
        }
      });
    }

    // Init board
    renderBoard(mount, function({hex, tolerance}){
      queryByColor(hex, tolerance).then(function(list){ render(list, hex, tolerance); });
    });

    // URL params
    try {
      var url = new url(location.href);
      var c = url.searchParams.get('color');
      var t = Number(url.searchParams.get('tol') || '18');
      if (c){
        var hex = '#' + c.toUpperCase();
        queryByColor(hex, t).then(function(list){ render(list, hex, t); });
        // smooth scroll into view
        var sec = document.getElementById('color-search'); if(sec) sec.scrollIntoView({behavior:'smooth', block:'start'});
      }
    } catch(e){}
  });
})();
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/rt-theme.js" defer>
</script>
<div aria-hidden="true" aria-labelledby="lm-title" aria-modal="true" class="modal" id="license-modal" role="dialog">
<div class="backdrop" data-close="">
</div>
<div class="box" tabindex="-1">
<button aria-label="关闭" class="close" data-close="" type="button">
     ✕
    </button>
<div class="header">
<div>
<div class="title" id="lm-title">
       确定授权类型
      </div>
<div class="crumbs">
<span id="lm-track-title">
        选中曲目
       </span>
       ·
       <span class="muted" id="lm-artist">
</span>
</div>
</div>
<a class="right-link" href="pricing.html" id="lm-sample" rel="noopener noreferrer" target="_blank">
      授权书范本
     </a>
</div>
<div class="body">
<nav aria-label="主导航" aria-orientation="vertical" class="tabs" role="tablist">
<button aria-selected="true" class="tab" data-plan="personal" role="tab" type="button">
       个人授权
      </button>
<button aria-selected="false" class="tab" data-plan="company" role="tab" type="button">
       企业授权
      </button>
<button aria-selected="false" class="tab" data-plan="company_plus" role="tab" type="button">
       企业PLUS授权
      </button>
<div class="hint">
       企业授权最高 6 折优惠
      </div>
</nav>
<section class="content">
<h4 id="lm-scope-title">
       个人授权范围
      </h4>
<ul class="scope" id="lm-scope-list">
<li class="ok">
<span class="dot">
         ✓
        </span>
<div class="desc">
         个人自媒体、短视频、直播、教程等
        </div>
</li>
<li class="ok">
<span class="dot">
         ✓
        </span>
<div class="desc">
         个人学习、交流、参赛、表演等音视频
        </div>
</li>
<li class="no">
<span class="dot">
         ✕
        </span>
<div class="desc">
         企业广告、宣传等推广音视频
        </div>
</li>
</ul>
</section>
</div>
<div class="footer">
<div class="assure">
<span class="icon">
       🛡️
      </span>
<span>
       50万保障金
      </span>
<span class="price">
<span id="lm-price">
        99
       </span>
<span style="font-size:14px; font-weight:600">
        元
       </span>
</span>
</div>
<div class="actions">
<button class="btn" id="lm-add-cart" type="button">
       加入购物车
      </button>
<button class="btn primary" id="lm-buy" type="button">
       立即购买
      </button>
</div>
</div>
</div>
</div>
<div aria-hidden="true" aria-labelledby="pay-title" aria-modal="true" class="modal" id="pay-modal" role="dialog">
<div class="backdrop" data-close="">
</div>
<div class="box" tabindex="-1">
<button aria-label="关闭" class="close" data-close="" type="button">
     ✕
    </button>
<div class="header">
<div class="title" id="pay-title">
      选择支付方式
     </div>
</div>
<div class="body">
<div class="pay-grid">
<button class="pay-btn" data-gw="paypal" type="button">
       PayPal
      </button>
<button class="pay-btn" data-gw="stripe" type="button">
       Stripe
      </button>
<button class="pay-btn" data-gw="wechat" type="button">
       微信支付 WeChat
      </button>
<button class="pay-btn" data-gw="alipay" type="button">
       支付宝 Alipay
      </button>
</div>
</div>
</div>
</div>
<script nonce="IClalsvn2VqRJlbO-283bg" src="config/payments.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/license-modal.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg">
   if('serviceWorker' in navigator){window.addEventListener('load',()=>{navigator.serviceWorker.register('/sw.js');});}
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/checkout-bridge.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/color-board-pro.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/rt-color-summary-inject.js" defer></script>
<script src="js/rt-scene-recommend.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/rt-color-button-overlay.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/rt-color-actions.js" defer>
</script>
<script id="rt-cb-patch" nonce="IClalsvn2VqRJlbO-283bg">
   (function(){
  document.addEventListener('DOMContentLoaded', function(){
    var mount = document.querySelector('#cb');
    var colorInput = document.querySelector('#cb-color-input');
    var hexInput = document.querySelector('#cb-hex-input');
    function normalizeHex(v){
      if(!v) return null;
      v = v.trim().toUpperCase();
      if(v[0] !== '#') v = '#'+v;
      if(/^#[0-9A-F]{6}$/.test(v)) return v;
      if(/^#[0-9A-F]{3}$/.test(v)) return '#'+v[1]+v[1]+v[2]+v[2]+v[3]+v[3];
      return null;
    }
    function persist(){ try{ localStorage.setItem('cb-last-hex', hexInput?.value || ''); }catch(e){} }
    function emit(){
      // try fire a custom event host page may listen to (if implemented there)
      try{
        document.dispatchEvent(new CustomEvent('cb:color-change', {detail:{hex: hexInput?.value}}));
      }catch(e){}
    }
    if(colorInput){
      colorInput.addEventListener('input', function(e){
        var hex = normalizeHex(e.target.value);
        if(!hex) return;
        if(hexInput) hexInput.value = hex;
        var vEl = mount && mount.querySelector('.cb-selected .value');
        if(vEl) vEl.textContent = hex;
        persist(); emit();
      });
    }
    if(hexInput){
      hexInput.addEventListener('change', function(e){
        var hex = normalizeHex(e.target.value);
        if(!hex) return;
        var vEl = mount && mount.querySelector('.cb-selected .value');
        if(vEl) vEl.textContent = hex;
        var cEl = document.querySelector('#cb-color-input');
        if(cEl) cEl.value = hex;
        persist(); emit();
      });
    }
  });
})();
  </script>
<section id="color-results-inline-wrap" style="display:none;max-width:1200px;margin:24px auto;padding:0 16px 48px 16px;">
<iframe id="color-results-frame" referrerpolicy="no-referrer" sandbox="allow-forms allow-same-origin allow-scripts" src="" style="width:100%;border:0;min-height:800px;" title="颜色匹配结果">
</iframe>
</section>
<script nonce="IClalsvn2VqRJlbO-283bg">
   // Provide a global hook for color summary to render inline instead of navigating
    (function(){
      const wrap = document.getElementById('color-results-inline-wrap');
      const frame = document.getElementById('color-results-frame');
      function resize(){
        try{
          if(!frame || !frame.contentDocument) return;
          const h = frame.contentDocument.documentElement.scrollHeight || 0;
          if(h) frame.style.height = (h+20)+'px';
        }catch(e){}
      }
      window.renderColorResultsInline = function(hex){
        try{
          if(!wrap || !frame) return false;
          wrap.style.display = 'block';
          frame.src='color-results.html?hex='+encodeURIComponent(hex||'');
          frame.addEventListener('load', function(){ setTimeout(resize, 50); setTimeout(resize, 200); setTimeout(resize, 800); }, { once:true });
          // Smoothly scroll to results
          try{ wrap.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
          return true;
        }catch(e){
          return false;
        }
      };
      // When theme changes, try to sync height again
      window.addEventListener('resize', function(){ setTimeout(resize, 100); });
    })();
  </script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="../contrast/contrast-theme.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="../contrast/contrast-palette-restore.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="../contrast/contrast-swatch-attr.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="../contrast/contrast-palette-even.js" defer>
</script>
<script nonce="IClalsvn2VqRJlbO-283bg" src="js/patch.palette-optim.js" defer>
</script>
<script src="js/a11y-and-motion.js" defer>
</script>
<script src="js/patch.cb-recent-fix.js" defer>
</script>
<script defer="" src="js/palette-click-force.js"></script>

<!-- Ruotong Account Buttons (auto-wired) -->
<script>window.RUOTONG_API_BASE = window.RUOTONG_API_BASE || '/api';</script>
<link rel="stylesheet" href="/rt-assets/css/toast.css">
<link rel="stylesheet" href="/rt-assets/css/modal.css">
<script src="/rt-assets/js/toast.js" defer></script>
<script src="/rt-assets/js/modal.js" defer></script>
<script src="/rt-assets/js/account-actions.js" defer></script>


<script>
document.addEventListener('DOMContentLoaded', function(){
  try{ document.body.classList.add('rt-darkfix'); }catch(e){}
});
</script>

<script src="js/rt-favorites.js" defer></script>
</body>
</html>